
#include "Interpreter.h"
#include <iostream>
#include <cassert>
#include <iterator>


extern 
bool shunting_yard(const std::vector<std::string>& infix, std::vector<std::string>& rpn);



int Interpreter::NaturalJoin3Tables(std::vector<std::string>& tables, 
	std::vector<std::string>& whereInfix, std::vector<Tuple>& resultTuples, 
	std::string& resultName, std::vector<std::string>& whereRPN, bool& selectionRemains)
{
	// check if the tables exist
	for(unsigned int i = 0; i < tables.size(); ++i)
	{
		if(mSchemaManager.relationExists(tables[i]) == false)
			return -1;
	}

	// need to figure out the join order
	// the whereRPN and selectionBeforeDisplay will be don't care
	
	std::vector<unsigned int> tableSizes(tables.size());
	unsigned int maxTableSize = 0, maxTableIdx, sumOfTableSizes = 0;
	//TODO: not sure if we'll need the max, total etc
	for(unsigned int i = 0; i < tableSizes.size(); ++i)
	{
		tableSizes[i] = mSchemaManager.getRelation(tables[i])->getNumOfBlocks();
		sumOfTableSizes += tableSizes[i];
		if(tableSizes[i] > maxTableSize)
		{
			maxTableSize = tableSizes[i];
			maxTableIdx = i;
		}
	}

	// find join sizes of all possible joins... n choose 2 possible joins
	std::vector<unsigned int> joinedSizes(tables.size()*(tables.size()-1)/2);
	unsigned int idx = 0, minSize = std::numeric_limits<unsigned int>::max(), minTbl1, minTbl2;
	for(unsigned int tbl1 = 0; tbl1 < tableSizes.size(); ++tbl1)
	{
		for(unsigned int tbl2 = tbl1 + 1; tbl2 < tableSizes.size(); ++tbl2)
		{
			joinedSizes[idx] = tableSizes[tbl1] * tableSizes[tbl2];

			if(joinedSizes[idx] < minSize)
			{
				minSize = joinedSizes[idx];
				minTbl1 = tbl1;
				minTbl2 = tbl2;
			}

			++idx;
		}
	}

	std::cout << "Joining tables: " << tables[minTbl1] << " and " << tables[minTbl2]
				<< " first..." << std::endl;


	std::vector<std::string> firstTables(2);
	firstTables[0] = tables[minTbl1];
	firstTables[1] = tables[minTbl2];

	std::string firstResultName;
	std::vector<Tuple> firstResultTuples;
	
	std::vector<std::string> firstWhereInfix;
	if(GetNaturalJoinIndicatorClause(whereInfix, firstTables, firstWhereInfix) != 0)
	{
		std::cout << "Failed to extract WHERE clasue" << std::endl;
		return -1;
	}

	// gather the join attribs to later modify the remaining WHERE clause
	std::string firstJoinAttrib, firstJoinAttrib1;
	if(firstWhereInfix[0].compare(firstTables[0]) == 0)
	{
		firstJoinAttrib = firstWhereInfix[2];
		firstJoinAttrib1 = firstWhereInfix[6];
	}
	else if(firstWhereInfix[0].compare(firstTables[1]) == 0)
	{
		firstJoinAttrib = firstWhereInfix[6];
		firstJoinAttrib1 = firstWhereInfix[2];
	}
	
	//////////////////////////////////
	// Now run the first natural join
	//////////////////////////////////
	int firstNaturalJoinRetVal = NaturalJoin(firstTables, firstWhereInfix, firstResultTuples, 
							firstResultName, whereRPN, selectionRemains);


	assert(whereRPN.empty());

	if(firstResultTuples.empty())
	{
		std::cout << "Natural Join of " << firstTables[0] << " and " << firstTables[1]
			<< " produced no result tuples... Exiting!" << std::endl;
		
		return 0;
	}

	for(unsigned int i = 0; i < firstResultTuples.size(); ++i)
		std::cout << firstResultTuples[i] << std::endl;
	

	// which table got left out?
	unsigned int lastTbl;
	for(lastTbl = 0; lastTbl < tables.size(); ++lastTbl)
	{
		if((lastTbl != minTbl1) && (lastTbl != minTbl2))
			break;
	}

	std::cout << "Now joining " << firstResultName << " with " << tables[lastTbl] << std::endl;


	//TODO: I don't care about the cost here, just write the first result table to disk 
	// and call those natural join functions again
	//////////////////////////////
	// WRITE FIRST RESULT TO DISK
	//////////////////////////////

	if(AppendTuplesToRelation(firstResultName, firstResultTuples) != 0)
	{
		std::cout << "Couldn't append tuples to relation: " << firstResultName
			<< "... Exiting!" << std::endl;

		return -1;
	}

	/////////////////////////
	// now generate the second where infix (the naturaj join indicator)
	// first modify the remaining where clause to reflect the joined table and attribs
	/////////////////////////

	std::cout << "Before modification, the whereInfix is: " << std::endl;
	std::copy(whereInfix.begin(), whereInfix.end(),
		std::ostream_iterator<std::string, char>(std::cout, " "));
	std::cout << std::endl;


	for(unsigned int i = 0; i < whereInfix.size(); ++i)
	{
		if(whereInfix[i].compare(".") == 0) // its a table
		{

			if(whereInfix[i-1].compare(firstTables[0]) == 0) // first table attrib
			{
				if(whereInfix[i+1].compare(firstJoinAttrib) == 0)
					whereInfix[i+1].append("01");
				else
					whereInfix[i+1].append("0");

				whereInfix[i-1].assign(firstResultName);
			}
			else if(whereInfix[i-1].compare(firstTables[1]) == 0) // second table attrib
			{
				if(whereInfix[i+1].compare(firstJoinAttrib1) == 0)
					whereInfix[i+1].append("01");
				else
					whereInfix[i+1].append("1");

				whereInfix[i-1].assign(firstResultName);

			}
			// else its a new table attrib

		}

	}

	std::cout << "After modification, the whereInfix is: " << std::endl;
	std::copy(whereInfix.begin(), whereInfix.end(),
		std::ostream_iterator<std::string, char>(std::cout, " "));
	std::cout << std::endl;

	std::vector<std::string> secondTables(2);
	secondTables[0].assign(firstResultName);
	secondTables[1].assign(tables[lastTbl]);
#if 0
	// arbitrarily choosing one clause to natural join, the other will be selected on display
	std::string secondWhereInfix;
	if(GetNaturalJoinIndicatorClause(whereInfix, secondTables, secondWhereInfix) != 0)
	{
		std::cout << "Failed to extract WHERE clasue" << std::endl;
		return -1;
	}
#endif


	//////////////////////////////////
	// Now run the second natural join
	//////////////////////////////////
	//std::vector<Tuple> secondResultTuples;
	std::vector<Tuple>& secondResultTuples = resultTuples;
	std::string secondResultName;
	int secondNaturalJoinRetVal = NaturalJoin(secondTables, whereInfix/*secondWhereInfix*/, 
							secondResultTuples, secondResultName, whereRPN, selectionRemains);

	//assert(whereRPN.empty() == false);

	if(secondResultTuples.empty())
	{
		std::cout << "Natural Join of " << secondTables[0] << " and " << secondTables[1]
			<< " produced no result tuples... Exiting!" << std::endl;
		
		return 0;
	}

	/*for(unsigned int i = 0; i < secondResultTuples.size(); ++i)
		std::cout << secondResultTuples[i] << std::endl;*/
	

	// the first result table is a temporary table, delete it here
	if(firstResultName.empty() == false)
		mSchemaManager.deleteRelation(firstResultName);

	resultName.assign(secondResultName);

	return 0;
}


int Interpreter::GetNaturalJoinIndicatorClause(std::vector<std::string>& whereInfix,
	std::vector<std::string>& tables,
	std::vector<std::string>& indicator)
{
	const static char* AND = "and";
	int pos, startPos = 0;
	do
	{
		for(pos = startPos; pos < whereInfix.size(); ++pos)
		{
			if(whereInfix[pos].compare(AND) == 0)
				break;
		}
		
		// startPos to pos is one sub-clause
		std::vector<unsigned int> tableNameLocs;
		for(unsigned int i = startPos; i < pos; ++i)
		{
			if(whereInfix[i].compare(".") == 0)
				tableNameLocs.push_back(i-1);
		}
		
		assert(tableNameLocs.size() == 2);

		// determine if this sub-clause is relevant
		bool relevant = false;
		if(((whereInfix[tableNameLocs[0]].compare(tables[0]) == 0) &&
			(whereInfix[tableNameLocs[1]].compare(tables[1]) == 0))
			|| ((whereInfix[tableNameLocs[0]].compare(tables[1]) == 0) &&
			(whereInfix[tableNameLocs[1]].compare(tables[0]) == 0)))
		{
			relevant = true;
		}

		// extract this clause if its relevant
		if(relevant)
		{
			indicator.assign(whereInfix.begin() + startPos, 
										whereInfix.begin() + pos);

			// update the whereInfix by removing the relevant sub-clause
			// either remove uptil and including the "and", or uptil end
			int eatStart, eatEnd;
			if(pos == whereInfix.size())
			{
				eatEnd = pos;

				// eat the AND before it, if any
				if((startPos > 0) && (whereInfix[startPos-1].compare(AND) == 0))
					eatStart = startPos-1;
				else
					eatStart = startPos;
			}
			else
			{
				// eat the AND after it
				eatEnd = pos+1;
				eatStart = startPos;
			}
			whereInfix.erase(whereInfix.begin() + eatStart,
								whereInfix.begin() + eatEnd);

			break; // we only need one relevant clause
		}
		else
			startPos = pos+1; // after "and"

	}
	while(startPos < whereInfix.size());

	if(indicator.empty() == false)
		return 0;
	else
		return -1;
}





















